package com.leyou.order.service;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.threadlocal.UserHolder;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.IdWorker;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.order.dto.CartDTO;
import com.leyou.order.dto.OrderDTO;
import com.leyou.order.entity.Order;
import com.leyou.order.entity.OrderDetail;
import com.leyou.order.entity.OrderLogistics;
import com.leyou.order.enums.OrderStatusEnum;
import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderLogisticsMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.utils.PayHelper;
import com.leyou.order.vo.OrderDetailVO;
import com.leyou.order.vo.OrderLogisticsVO;
import com.leyou.order.vo.OrderVO;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.AddressDTO;
//import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private OrderLogisticsMapper logisticsMapper;
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private UserClient userClient;
    @Autowired
    private PayHelper payHelper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 创建订单接口
     * @param orderDTO      订单DTO对象
     * @return              订单编号
     */
    //@GlobalTransactional  //分布式事务控制注解
    @Transactional
    public Long createOrder(OrderDTO orderDTO) {
        Order order = new Order();
        // 1、生成订单编号
        long orderId = idWorker.nextId();
        // 2、保存数据到订单表
        order.setOrderId(orderId);
        order.setPostFee(0L); // 全场包邮
        order.setPaymentType(orderDTO.getPaymentType()); // 页面有
        order.setUserId(UserHolder.getUser().getId()); // 获取当前线程上的用户id
        order.setStatus(OrderStatusEnum.INIT.value()); // 初始化未付款
        order.setInvoiceType(0); // 发票类型(0无发票1普通发票，2电子发票，3增值税发票)
        order.setSourceType(2); //订单来源：1:app端，2：pc端，3：微信端
        // =======================================================================================
        // 2.0 计算总金额： （每个sku的价格 * 数量 ） 然后再全部相加起来就是总金额
        List<CartDTO> carts = orderDTO.getCarts();
        // 2.1 为了能够通过skuid获取他购买的数量，我们可以把List集合转成Map
        Map<Long, Integer> cartMap = carts.stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
        // 2.2 收集skuid
        List<Long> skuIds = carts.stream().map(CartDTO::getSkuId).collect(Collectors.toList());
        // 2.3 远程调用商品微服务查询sku
        List<SkuDTO> skuDTOList = itemClient.querySkuListByIds(skuIds);
        // 2.4 收集每个sku的价格 ,最后求和
        long totalFee = skuDTOList.stream().mapToLong(skuDTO -> skuDTO.getPrice() * cartMap.get(skuDTO.getId())).sum();
        // =======================================================================================
        order.setTotalFee(totalFee); // TODO 总金额
        order.setActualFee(1L);  // TODO 注意：实际支付 = 总金额 - 活动金额；现在为了付款所以写死1分钱
        int count = orderMapper.insertSelective(order);
        if(count != 1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        // 3、保存数据到订单条目表：批量
        List<OrderDetail> detailList = new ArrayList<>();
        for (SkuDTO s : skuDTOList) {
            // 创建订单条目
            OrderDetail od = new OrderDetail();
            od.setId(idWorker.nextId());
            od.setOrderId(orderId);
            od.setSkuId(s.getId());
            od.setTitle(s.getTitle());
            od.setNum(cartMap.get(s.getId())); // 数量
            od.setPrice(s.getPrice());
            od.setOwnSpec(s.getOwnSpec());
            od.setImage(StringUtils.substringBefore(s.getImages(),","));// 指的是截取到逗号之前的字符串
            od.setCreateTime(new Date());
            od.setUpdateTime(new Date());
            detailList.add(od);
        }
        count = orderDetailMapper.insertList(detailList);// 批量插入
        if(count != skuDTOList.size()){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        // 4、保存物流表的信息
        AddressDTO addressDTO = userClient.queryAddressById(UserHolder.getUser().getId(), orderDTO.getAddressId());
        OrderLogistics orderLogistics = BeanHelper.copyProperties(addressDTO, OrderLogistics.class);
        orderLogistics.setOrderId(orderId);
        count = logisticsMapper.insertSelective(orderLogistics);
        if(count != 1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        // 5、减去库存
        /**
         * 为什么把减库存：远程调用减库存放到最后呢？
         *      1、feign的远程调用，如果出现了异常会把异常返回到当前的方法中
         *      2、因为当前方法有事务，在前面有三次的数据库的操作，如果最后一次报错，整个方法都会回滚，避免了分布式事务问题【分布式事务性能不能好】
         */
        itemClient.minusStock(cartMap);

        /**
         *  如果feign正常执行了，但是执行了feign接口之后，出异常，这个时候，这个方法会回滚，但是feign接口回滚不了
         */
        //int i = 1/0;  //模拟异常

        // 6、返回订单编号
        return orderId;
    }

    /**
     * 根据id查询订单
     * @param id
     * @return
     */
    public OrderVO queryOrderById(Long id) {
        // 1、查询订单
        Order order = orderMapper.selectByPrimaryKey(id);
        // 1.1 判空
        if(order == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 1.2 判断订单是谁的？
        if(!UserHolder.getUser().getId().equals(order.getUserId())){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 2、查询订单详情
        OrderDetail record = new OrderDetail();
        record.setOrderId(order.getOrderId());
        List<OrderDetail> orderDetailList = orderDetailMapper.select(record);
        if(CollectionUtils.isEmpty(orderDetailList)){
            throw new LyException(ExceptionEnum.ORDER_DETAIL_NOT_FOUND);
        }
        // 3、查询物流信息
        OrderLogistics orderLogistics = logisticsMapper.selectByPrimaryKey(order.getOrderId());
        if(orderLogistics==null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 4、封装OrderVO返回
        OrderVO orderVO = BeanHelper.copyProperties(order, OrderVO.class);
        orderVO.setLogistics(BeanHelper.copyProperties(orderLogistics, OrderLogisticsVO.class));
        orderVO.setDetailList(BeanHelper.copyWithCollection(orderDetailList, OrderDetailVO.class));

        // 5、返回信息
        return orderVO;
    }

    /**
     * 获取微信的预支付链接
     *
     * @param id
     * @return
     */

    private final static String KEY_PREFIX = "order:pay:oid:"; // redis前缀

    public String getPayUrl(Long id) {
        // redis中key
        String key = KEY_PREFIX + id;
        try {
            // 先去redis获取预支付链接
            String cacheURL = redisTemplate.opsForValue().get(key);
            if(StringUtils.isNotBlank(cacheURL)){
                return cacheURL;
            }
        } catch (Exception e) {
            log.error("【订单微服务】获取预支付链接失败，redis可能挂了，通知管理员！", e);
        }
        // 根据id查询订单
        Order order = orderMapper.selectByPrimaryKey(id);
        if(order == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 判断订单的状态:  状态不是1 ，抛异常
        if(!OrderStatusEnum.INIT.value().equals(order.getStatus())){
            throw new LyException(ExceptionEnum.INVALID_ORDER_STATUS);
        }

        // 根据订单id查询描述信息
        OrderDetail record = new OrderDetail();
        record.setOrderId(id);
        List<OrderDetail> orderDetails = orderDetailMapper.select(record);
        if(CollectionUtils.isEmpty(orderDetails)){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 获取描述信息
        String desc = orderDetails.stream().map(OrderDetail::getTitle).collect(Collectors.joining("|"));
        if(StringUtils.isNotBlank(desc) && desc.length() > 120){
            desc = StringUtils.substring(desc, 0, 120);
        }
        /**
         * 参数一：订单号
         * 参数二：总金额
         * 参数三：描述
         */
        String payUrl = payHelper.getPayUrl(id, order.getActualFee(), desc);

        try {
            // 获取到预支付链接，要把这个链接先存入redis中，预支付链接有效期是两个小时
            redisTemplate.opsForValue().set(key,payUrl);
        } catch (Exception e) {
            log.error("【订单微服务】往redis中存入预支付链接失败，redis可能挂了，通知管理员！", e);
        }
        // 返回预支付链接
        return payUrl;
    }

    /**
     * 微信通知我们之后，要修改订单状态
     * @param data：微信给到我们的支付结果
     */
    @Transactional
    public void payNotify(Map<String, String> data) {
        // 1、检测结果
        payHelper.checkResultCode(data);
        // 2、检查签名：判断一下是否是微信官方
        payHelper.checkSignature(data);
        // 3、到这一步就是支付成功：获取支付结果
        // 3.1 订单编号
        String order_id = data.get("out_trade_no");
        // 3.2 支付金额
        String total_fee = data.get("total_fee");
        // 3.3 校验这两个值
        if(StringUtils.isBlank(order_id) || StringUtils.isBlank(total_fee)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 3.4 把字符串转成数字
        Long orderId = Long.valueOf(order_id);
        Long totalFee = Long.valueOf(total_fee);
        // 4、根据结果的订单号去查询订单
        Order order = orderMapper.selectByPrimaryKey(orderId);
        if(order == null){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 4.1 金额校验:  我们应该跟实际金额比对
        if(!StringUtils.equals(total_fee, order.getActualFee()+"")){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 4.2 幂等性校验：判断是否已经支付过了
        if(!OrderStatusEnum.INIT.value().equals(order.getStatus())){
            log.warn("【微信异步通知】订单已经支付，我们不用修改订单状态！");
            return ; // 已经支付就不用更新数据库
        }

        // 5、如果订单存在把订单状态改为已支付
        // 5.1 改变状态
        order.setStatus(OrderStatusEnum.PAY_UP.value());
        // 5.2 执行更新操作
        int count = orderMapper.updateByPrimaryKeySelective(order);
        if(count != 1){
            throw new LyException(ExceptionEnum.UPDATE_OPERATION_FAIL);
        }
        log.info("【微信异步通知乐优】更新订单状态为已支付成功！");
    }

    /**
     * 查询订单的状态
     * @param id
     * @return
     */
    public Integer orderState(Long id) {
        Order order = orderMapper.selectByPrimaryKey(id);
        if(order == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        return order.getStatus();
    }
}






